﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;

namespace FolderData
{
    public abstract class Substance : ICloneable
    {
        #region Definitions

        public static readonly int LayerCount = 3;

        public enum LayerOrder
        {
            FIRST = 0,
            SECOND = 1,
            THIRD = 2
        }
        public enum SubstanceState
        {
            NOT_SPECIFIED,
            VALIDATED,
            VALIDATING,
            INVALIDATED,
            INVALIDATING
        }

        [Flags]
        public enum Properties : ulong
        {
            Movable = 0x1,
            Foldable = 0x2,
            Seperatable = 0x4,
            Foldforbidden = 0x8,
            UnfoldBehavior = 0x10,
            VertualGeom = 0x20,
        }
        #endregion

        #region DataSet
        public class DataSet
        {
            protected string _id;
            protected string _name;
            protected LayerOrder _layer;
            protected Vector2 _position;
            protected float _rotation;

            protected Properties _property;

            [ContentSerializerIgnore]
            public Properties Property
            {
                get { return _property; }
                set { _property = value; }
            }

            protected Substance _tag;
            protected bool _isLocked;
            protected OnCollisionEvent _OnCollision;

            public string id
            {
                get { return _id; }
                set { _id = value; }
            }
            [ReadOnly(true)]
            public string Name
            {
                get { return _name; }
                set { _name = value; }
            }
            public LayerOrder Layer
            {
                get { return _layer; }
                set { _layer = value; }
            }
            public Vector2 Position
            {
                get { return _position; }
                set { _position = value; }
            }
            public float Rotation
            {
                get { return _rotation; }
                set { _rotation = value; }
            }

            public bool IsMovable
            {
                get { return (_property & Properties.Movable) == Properties.Movable; }
                set { _property = value ? _property | Properties.Movable : (_property|Properties.Movable) ^ Properties.Movable; }
            }
            public bool IsFoldable
            {
                get { return (_property & Properties.Foldable) == Properties.Foldable; }
                set { _property = value ? _property | Properties.Foldable : (_property | Properties.Foldable) ^ Properties.Foldable; }
            }
            public bool IsSeperatable
            {
                get { return (_property & Properties.Seperatable) == Properties.Seperatable; }
                set { _property = value ? _property | Properties.Seperatable : (_property | Properties.Seperatable) ^ Properties.Seperatable; }
            }
            public bool IsFoldForbidden
            {
                get { return (_property & Properties.Foldforbidden) == Properties.Foldforbidden; }
                set { _property = value ? _property | Properties.Foldforbidden : (_property | Properties.Foldforbidden) ^ Properties.Foldforbidden; }
            }
            public bool IsUnfoldBehavior
            {
                get { return (_property & Properties.UnfoldBehavior) == Properties.UnfoldBehavior; }
                set { _property = value ? _property | Properties.UnfoldBehavior : (_property | Properties.UnfoldBehavior) ^ Properties.UnfoldBehavior; }
            }
            public bool IsVirtualGeom
            {
                get { return (_property & Properties.VertualGeom) == Properties.VertualGeom; }
                set { _property = value ? _property | Properties.VertualGeom : (_property | Properties.VertualGeom) ^ Properties.VertualGeom; }
            }

            [DescriptionAttribute("true이면 물체가 마우스나 키보드의 입력에 움직이지 않습니다.")]
            public bool IsLocked
            {
                get { return _isLocked; }
                set { _isLocked = value; }
            }
            public OnCollisionEvent OnCollision
            {
                get { return _OnCollision; }
                set { _OnCollision = value; }
            }
            
            /// <summary>
            /// 이 DataSet과 연동된 Substance로의 reference
            /// MapEditor에서만 사용됩니다.
            /// </summary>
            [ContentSerializerIgnore]
            [Browsable(false)]
            public Substance Tag
            {
                get { return _tag; }
                set { _tag = value; }
            }

            public virtual void Serialize(ContentWriter output)
            {
                output.Write(id);
                output.Write(Name);
                output.Write((int)Layer);
                output.Write(Position.X);
                output.Write(Position.Y);
                output.Write(Rotation);
                //접기 관련 특성들
                output.Write((UInt64)_property);

                output.Write(IsLocked);
                output.Write((int)OnCollision);
            }
            public virtual void Deserialize(ContentReader input)
            {
                this.id = input.ReadString();
                this.Name = input.ReadString();
                this.Layer = (LayerOrder)input.ReadInt32();
                this.Position = new Vector2(input.ReadSingle(), input.ReadSingle());
                this.Rotation = input.ReadSingle();
                //접기 관련 특성들
                this._property = (Properties)input.ReadUInt64();

                this.IsLocked = input.ReadBoolean();
                this.OnCollision = (OnCollisionEvent)input.ReadInt32();
            }
        }

        protected delegate void NextStateCallBack(SubstanceState state);
        #endregion

        #region Fields
        /* Property 추가시 주의점:
         * 1. XMLSerialization이 필요한 경우 Get/Set Accessor를 만든다.
         * 2. Clone()함수에서 해당 Property도 복사하도록 한다.
         * 3. ContentWriter / Reader에서 해당 Property를 저장/읽기를 하도록 한다.
         * 4. DataSet에 추가해야 되는 것이면 DataSet에 추가하고 그와 관련된 모든 Accessor, ContentProcess를 다 수정한다. (상속 받은 것 까지..)
         * 
         * 이 모든 것은 모든 Substance 상속 클래스에 해당한다.
         */
        protected string _name;
        protected LayerOrder _layer;
        protected Properties _property;

        protected PhysicsSimulator _simulator;
        protected Body _body;
        protected Geom _geom;
        protected Vector2 _origin;
        protected Vertices _verts;
        protected float _mass;
        protected Vertices _textureVerts;
        protected Color _colorMod;              //clone ignore
        protected bool _isBound;                //clone ignore
        protected SubstanceState _state;        //clone ignore
        protected OnCollisionEvent _OnCollision;

        /// <summary>
        /// Map Editor와 ResourceManager에서 Substance를 구분하기 위한 식별자
        /// </summary>
        [ReadOnly(true)]
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        [Browsable(false)]
        public virtual Vertices Verts
        {
            get { return _verts; }
            set { _verts = value; }
        }
        [Browsable(false)]
        public Vertices TextureVerts
        {
            get { return _textureVerts; }
            set { _textureVerts = value; }
        }
        [ReadOnly(true)]
        public float Mass
        {
            get { return _mass; }
            set 
            {
                _mass = value;
                if (_body != null)
                    _body.Mass = value;
            }
        }
        [ReadOnly(true)]
        public LayerOrder Layer
        {
            get { return _layer; }
            set { _layer = value; }
        }


        [ReadOnly(true)]
        public bool IsMovable
        {
            get { return (_property & Properties.Movable) == Properties.Movable; }
            set { _property = value ? _property | Properties.Movable : (_property | Properties.Movable) ^ Properties.Movable; }
        }
        [ReadOnly(true)]
        public bool IsFoldable
        {
            get { return (_property & Properties.Foldable) == Properties.Foldable; }
            set { _property = value ? _property | Properties.Foldable : (_property | Properties.Foldable) ^ Properties.Foldable; }
        }
        [ReadOnly(true)]
        public bool IsSeperatable
        {
            get { return (_property & Properties.Seperatable) == Properties.Seperatable; }
            set { _property = value ? _property | Properties.Seperatable : (_property | Properties.Seperatable) ^ Properties.Seperatable; }
        }
        [ReadOnly(true)]
        public bool IsFoldForbidden
        {
            get { return (_property & Properties.Foldforbidden) == Properties.Foldforbidden; }
            set { _property = value ? _property | Properties.Foldforbidden : (_property | Properties.UnfoldBehavior) ^ Properties.Foldforbidden; }
        }
        [ReadOnly(true)]
        public bool IsUnfoldBehavior
        {
            get { return (_property & Properties.UnfoldBehavior) == Properties.UnfoldBehavior; }
            set { _property = value ? _property | Properties.UnfoldBehavior : (_property | Properties.UnfoldBehavior) ^ Properties.UnfoldBehavior; }
        }
        [ReadOnly(true)]
        public bool IsVirtualGeom
        {
            get { return (_property & Properties.VertualGeom) == Properties.VertualGeom; }
            set { _property = value ? _property | Properties.VertualGeom : (_property | Properties.VertualGeom) ^ Properties.VertualGeom; }
        }

        [ContentSerializerIgnore]
        [ReadOnly(true)]
        public OnCollisionEvent OnCollision { get { return _OnCollision; } set { _OnCollision = value; } }

        [ContentSerializerIgnore]
        [Browsable(false)]
        public Vector2 Position
        {
            set { _body.Position = value; }
            get { return _body.Position; }
        }
        [ContentSerializerIgnore]
        [Browsable(false)]
        public float Rotation
        {
            get { return _body.Rotation; }
            set { _body.Rotation = value; }
        }
        [ContentSerializerIgnore]
        [Browsable(false)]
        public Vector2 Origin { get { return _origin; } }

        [ContentSerializerIgnore]
        [Browsable(false)]
        public Body Body { get { return _body; } }

        [ContentSerializerIgnore]
        [Browsable(false)]
        public Geom Geom { get { return _geom; } }

        [ContentSerializerIgnore]
        [Browsable(false)]
        public abstract Texture2D Texture
        {
            get;
        }
        [ContentSerializerIgnore]
        [Browsable(false)]
        public abstract Rectangle? Rect
        {
            get;
        }
        [ContentSerializerIgnore]
        [Browsable(false)]
        public virtual bool Activation
        {
            get
            {
                if (_state == SubstanceState.VALIDATED || _state == SubstanceState.VALIDATING)
                    return true;
                else if (_state == SubstanceState.INVALIDATED || _state == SubstanceState.INVALIDATING)
                    return false;
                else
                    throw new Exception("State가 Validation과 관련되지 않았습니다.");
            }
            set
            {
                //set 부분만 각각의 Subclass들이 구현한다.
                throw new NotImplementedException();
            }
        }

        [ContentSerializerIgnore]
        [Browsable(false)]
        public Color ColorMod
        {
            get { return _colorMod; }
            set { _colorMod = value; }
        }
        [ContentSerializerIgnore]
        [Browsable(false)]
        public bool IsBound
        {
            get { return _isBound; }
        }

        #endregion

        /// <summary>
        /// deprecated!
        /// Multi Body와 Geom을 갖는 경우 이 함수를 이용하면 모든 Body와 Geom이 Simulator에 등록되므로
        /// 이 메소드를 사용하지 말고 simulator를 인자로 받지 않는 동일 함수를 이용하고 필요한 Body와 Geom만
        /// Physics Simulator에 등록하세요.
        /// </summary>
        /// <param name="simulator"></param>
        /// <param name="mass"></param>
        /// <param name="vertices"></param>
        /// <param name="body"></param>
        /// <param name="geom"></param>
        /// <param name="origin"></param>
        protected void makeBodyGeom(PhysicsSimulator simulator, float mass, Vector2[] vertices, out Body body, out Geom geom, out Vector2 origin, out Vertices verts)
        {
            verts = new Vertices(vertices);
            origin = verts.GetCentroid();
            body = BodyFactory.Instance.CreatePolygonBody(_simulator, verts, mass);
            geom = GeomFactory.Instance.CreatePolygonGeom(_simulator, body, verts, 0f);
            geom.Tag = (object)this;
        }
        protected void makeBodyGeom(float mass, Vertices vertices, out Body body, out Geom geom, out Vector2 origin)
        {
            Vertices verts = new Vertices();
            for (int i = 0; i < vertices.Count; ++i)
                verts.Add(vertices[i]);

            origin = verts.GetCentroid();
            body = BodyFactory.Instance.CreatePolygonBody(verts, mass);
            geom = GeomFactory.Instance.CreatePolygonGeom(body, verts, 0f);
            geom.Tag = (object)this;
        }

        /// <summary>
        /// Sprite를 받아 Body와 Geom을 자동 생성.
        /// </summary>
        /// <param name="mass"></param>
        /// <param name="sprite"></param>
        /// <param name="body"></param>
        /// <param name="geom"></param>
        /// <param name="origin"></param>
        protected void makeBodyGeom(float mass, Texture2D sprite, out Body body, out Geom geom, out Vector2 origin)
        {
            uint[] data = new uint[sprite.Width * sprite.Height];
            sprite.GetData(data);
            Vertices verts = Vertices.CreatePolygon(data, sprite.Width, sprite.Height);
            origin = verts.GetCentroid();
            body = BodyFactory.Instance.CreatePolygonBody(verts, mass);
            geom = GeomFactory.Instance.CreatePolygonGeom(body, verts, 0f);
            geom.Tag = (object)this;
        }

        public abstract void Update(GameTime gameTime);

        public abstract object Clone();
        /// <summary>
        /// Substance가 가지고 있는 Property들을 복사합니다. (shallow copy)
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dest"></param>
        protected static void _clone(Substance src, Substance dest)
        {
            dest._property = src._property;
            dest.Mass = src.Mass;
            dest.Name = src.Name;
            //dest.Position = src.Position; //BindSimulator에서 처리할 것임
            //dest.Rotation = src.Rotation; //BindSimulator에서 처리할 것임
            dest.Verts = src.Verts;
            dest.TextureVerts = src.TextureVerts;
            dest.Layer = src.Layer;
            dest.ColorMod = src.ColorMod;
            dest.OnCollision = src.OnCollision;
        }

        /// <summary>
        /// DataSet이 규정하는 대로 데이터를 설정 합니다.
        /// </summary>
        /// <param name="ds"></param>
        protected void SetDatas(DataSet ds)
        {
            if (!ds.Name.Equals(this.Name))
                throw new ArgumentException("주어진 DataSet과 이 오브젝트의 이름이 일치하지 않습니다.");

            this._property = ds.Property;

            this.Layer = ds.Layer;
            this.Position = ds.Position;
            this.Rotation = ds.Rotation;
            this.OnCollision = ds.OnCollision;
        }

        /// <summary>
        /// 모든 Substance간 공통되는 Data들을 DataSet에 설정합니다.
        /// </summary>
        /// <param name="ds"></param>
        protected void _getDatas(DataSet ds)
        {
            ds.Name = this.Name;

            ds.Property = this._property;

            ds.Layer = this.Layer;
            ds.Position = this.Position;
            ds.Rotation = this.Rotation;
            ds.OnCollision = this.OnCollision;
        }

        /// <summary>
        /// 이 Substance의 Body와 Geom을 만들고 그것을 입력 받은 simulator와 연결합니다.
        /// Map에서 부르게 됩니다. Clone을 하고 나서도 사용 됩니다.
        /// </summary>
        /// <param name="simulator"></param>
        public virtual void BindSimulator(PhysicsSimulator simulator)
        {
            _simulator = simulator;
            _isBound = true;
        }

        public virtual void UnbindSimulator()
        {
            _isBound = false;
        }

        public Substance()
        {
            _colorMod = Color.White;
            _state = SubstanceState.NOT_SPECIFIED;
        }

        /// <summary>
        /// Deprecated. Substance의 생성자는 기본 생성자만 사용합니다.
        /// Substance의 생성은 모두 XML Deserialization을 통해 된다고 가정합니다.
        /// Substance를 상속하는 모든 객체의 생성은 다음과 같은 방식을 따릅니다.
        /// ResourceManager의 생성 (XML Deserialization) -> 각종 Substance의 상속클래스들의 생성 (XML Deserialization) 
        /// 생성된 object들을 ResourceManager가 관리.
        /// 해당 object가 필요할 때는 Resourcemanager.Load<>() 함수를 이용. Load<>()함수 에서는 해당 object를 Clone하여 준다.
        /// 그리고 그 object의 Geom과 Body를 사용하려는 Map에서 Physics Simulator에 add하여 사용한다.
        /// </summary>
        /// <param name="simulator"></param>
        protected Substance(PhysicsSimulator simulator)
        {
            _mass = -1;
            _simulator = simulator;
        }

        /// <summary>
        /// Deprecated
        /// </summary>
        /// <param name="simulator"></param>
        /// <param name="mass"></param>
        /// <param name="vertices"></param>
        protected Substance(PhysicsSimulator simulator, float mass, Vector2[] vertices)
        {
            _mass = -1;
            _simulator = simulator;
            //makeBodyGeom(simulator, mass, vertices, out _body, out _geom, out _origin);
        }
    }
}